/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.virtual.model.jcr;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ItemVisitor;
import javax.jcr.MergeException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.Lock;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.version.Version;
import javax.jcr.version.VersionException;
import javax.jcr.version.VersionHistory;
import offset.nodes.Constants;
import offset.nodes.client.model.StringArrayConverter;

/**
 *
 * @author Walter Lütgenau
 */
public class SimpleNode implements Node {
    String name = null;
    String nodeType = "";
    SimpleNode parent = null;
    ArrayList<Node> children = new ArrayList<Node>();
    HashMap properties = new HashMap();
    NodePerTypeFactory factory = null;
    NodeDefinition definition;
    String path = null;
    public static final int FORMAT_JCR = 1;
    public static final int FORMAT_USER = 2;
    int formatMode = FORMAT_JCR;
    public static final int COPYMODE_COPY_CHILDREN = 1;
    public static final int COPYMODE_DO_NOT_COPY_CHILDREN = 2;
    int copyMode = COPYMODE_COPY_CHILDREN;
    
    public SimpleNode(String name) {
        this.name = name;
    }
    
    public SimpleNode(String name, SimpleNode parent) {
        this.name = name;
        this.parent = parent;
    }
    
    public SimpleNode() {        
    }
    
    public SimpleNode(Node original) throws RepositoryException {
        copyNode(this, original);
    }
    
    public SimpleNode(Node original, int formatMode, int copyMode) throws RepositoryException {
        this.formatMode = formatMode;
        this.copyMode = copyMode;
        copyNode(this, original);
    }

    protected SimpleNode copyNode(SimpleNode copy, Node original) throws RepositoryException {
        copy.setName(original.getName());
        copy.setPath(original.getPath());
        copy.setNodeType(original.getPrimaryNodeType().getName());
        copy.setDefinition(new SimpleNodeDefinition(original.getDefinition()));
        
        PropertyIterator pi = original.getProperties();
        while(pi.hasNext()) {
            Property property = pi.nextProperty();
            
            if (isMultiple(original, property.getName())) {
                StringArrayConverter sa = new StringArrayConverter(property.getValues());
                if (formatMode == FORMAT_USER)
                    sa = new StringArrayConverter(PropertyValueFormatter.format(property.getValues()));
                copy.setProperty(property.getName(), sa.toString());
            }
            else {             
                String value = property.getString();
                if (formatMode == FORMAT_USER)
                    value = PropertyValueFormatter.format(property.getValue());
                copy.setProperty(property.getName(), value);
            }
        }

        if (copyMode == COPYMODE_DO_NOT_COPY_CHILDREN)
            return copy;
        
        NodeIterator ni = original.getNodes();
        while(ni.hasNext()) {
            Node child = ni.nextNode();
            copy.addNode(copyNode(new SimpleNode(), child));
        }
        
        return copy;        
    }

    public String getNodeType() {
        return nodeType;
    }
    
    public boolean isMultiple(Node node, String propertyName) throws RepositoryException {
        Boolean result = isMultiple(node.getPrimaryNodeType(), propertyName);
        if (result == null) {
            for (NodeType type : node.getMixinNodeTypes()) {
                result = isMultiple(type, propertyName);
                if (result != null)
                    break;
            }
        }
        
        if (result != null && result.booleanValue())
            return true;

        return false;
    }

    public Boolean isMultiple(NodeType type, String propertyName) throws RepositoryException {
        PropertyDefinition[] propertyDefinitions = type.getPropertyDefinitions();
        for (PropertyDefinition propertyDefinition : propertyDefinitions) {
            if (propertyDefinition.getName().equals(propertyName))
                if (propertyDefinition.isMultiple())
                    return new Boolean(true);
                else
                    return new Boolean(false);
        }

        for (NodeType supertype : type.getSupertypes()) {
            Boolean result = isMultiple(supertype, propertyName);
            if (result != null)
                return result;
        }
            
        return null;
    }


    public void setNodeType(String nodeType) {
        this.nodeType = nodeType;
    }
    
    public void setNodePerTypeFactory(NodePerTypeFactory factory) {
        this.factory = factory;
    }
    
    public void addNode(SimpleNode node) {
        children.add(node);
        node.setParent(this);
    }
    
    public void accept(ItemVisitor arg0) throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Item getAncestor(int arg0) throws ItemNotFoundException, AccessDeniedException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public int getDepth() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        if (path != null) {
            int last = path.lastIndexOf("/");
            if (last >= 0) {
                path = path.substring(0, last) + "/" + name;
            }
        }
    }

    public Node getParent() {
        return parent;
    }
    
    public void setParent(SimpleNode parent) {
        this.parent = parent;
    }
    
    public void setPath(String path) {
        this.path = path;
    }

    public String getPath() throws RepositoryException {
        if (path != null)
            return path;
        
        LinkedList names = new LinkedList();
        names.add(getName());
        
        Node node = this;
        while( node.getParent() != null) {
            node = node.getParent();
            names.addFirst(node.getName());
        }
        
        StringBuffer buf = new StringBuffer();
        Iterator i = names.iterator();
        while(i.hasNext()) {
            String name = (String) i.next();
            buf.append(name);
            if ( i.hasNext())
                buf.append("/");
        }
        
        return buf.toString();
    }

    public Session getSession() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean isModified() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean isNew() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean isNode() {
        return true;
    }

    public boolean isSame(Item arg0) throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void refresh(boolean arg0) throws InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void remove() throws VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException, InvalidItemStateException, ReferentialIntegrityException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addMixin(String arg0) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Node addNode(String name) {
        SimpleNode child = new SimpleNode(name, this);
        children.add(child);
        
        return child;
    }

    public Node addNode(String name, String nodeType) throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException, ConstraintViolationException, RepositoryException {
        Node child = null;
        if (factory == null) { // create a SimpleNode
            child = new SimpleNode(name, this);
            ((SimpleNode)child).setNodeType(nodeType);
            children.add(child);
        } else {
            // use factory to create node. 
            child = factory.newNode(nodeType);
            if (child instanceof SimpleNode) { // in most cases, this will be a class derived from SimpleNode
                ((SimpleNode) child).setParent(this);
                ((SimpleNode) child).setName(name);
            }
            children.add(child);
        }

        return child;
    }

    public boolean canAddMixin(String arg0) throws NoSuchNodeTypeException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void cancelMerge(Version arg0) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Version checkin() throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void checkout() throws UnsupportedRepositoryOperationException, LockException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void doneMerge(Version arg0) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Version getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public String getCorrespondingNodePath(String arg0) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void setDefinition(NodeDefinition definition) {
        this.definition = definition;
    }

    public NodeDefinition getDefinition() throws RepositoryException {
        return definition;
    }

    public int getIndex() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public NodeType[] getMixinNodeTypes() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Node getNode(String name) throws PathNotFoundException, RepositoryException {
        Iterator<Node> i = children.iterator();
        while( i.hasNext()) {
            Node node = i.next();
            if ( node.getName().equals(name))
                return node;
        }
        
        return null;
    }

    public NodeIterator getNodes() {
        return new SimpleNodeIterator(children);
    }

    /**
     * We support this only with exact match. Hence getNodes(name) can never be used with a pattern.
     * 
     * @param name
     * @return
     * @throws javax.jcr.RepositoryException
     */
    public NodeIterator getNodes(String name) throws RepositoryException {
        LinkedList<Node> result = new LinkedList<Node>();
        
        Iterator<Node> i = children.iterator();
        while( i.hasNext()) {
            Node node = i.next();
            if ( node.getName().equals(name))
                result.add(node);
        }
        
        return new SimpleNodeIterator(result);
    }

    public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public NodeType getPrimaryNodeType() throws RepositoryException {
        return new SimpleNodeType(nodeType);
    }

    public PropertyIterator getProperties() throws RepositoryException {
        return new SimplePropertyIterator(properties.values());
    }

    public PropertyIterator getProperties(String arg0) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property getProperty(String name) {
        if (name.equals(Constants.JCR_PRIMARY_TYPE))
            return new SimpleProperty(name, getNodeType());
        return (SimpleProperty) properties.get(name);
    }

    public void removeProperty(String name) {
        properties.remove(name);
    }

    public PropertyIterator getReferences() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean hasNode(String arg0) throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean hasNodes() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean hasProperties() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean hasProperty(String name) {
        return properties.containsKey(name);
    }

    public boolean holdsLock() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean isCheckedOut() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean isLocked() throws RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public boolean isNodeType(String type) throws RepositoryException {
        return nodeType.equals(type);
        //return getProperty(Constants.JCR_PRIMARY_TYPE).getString().equals(nodeType);
    }

    public Lock lock(boolean arg0, boolean arg1) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public NodeIterator merge(String arg0, boolean arg1) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void orderBefore(String arg0, String arg1) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void removeMixin(String arg0) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void restore(String arg0, boolean arg1) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void restore(Version arg0, boolean arg1) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void restore(Version arg0, String arg1, boolean arg2) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void restoreByLabel(String arg0, boolean arg1) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String name, SimpleProperty property) {
        properties.put(name, property);
        return property;
    }

    public Property setProperty(String arg0, Value arg1) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String arg0, Value arg1, int arg2) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String arg0, Value[] arg1) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String arg0, Value[] arg1, int arg2) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String name, String[] value) {
        return setProperty(name, new SimpleProperty(name, value));
    }

    public Property setProperty(String arg0, String[] arg1, int arg2) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String name, String value) {
        return setProperty(name, new SimpleProperty(name, value));
    }

    public Property setProperty(String arg0, String arg1, int arg2) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String arg0, InputStream arg1) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        return setProperty(name, new SimpleProperty(name, value));
    }

    public Property setProperty(String arg0, double arg1) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String arg0, long arg1) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    public Property setProperty(String arg0, Calendar arg1) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public Property setProperty(String arg0, Node arg1) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void update(String arg0) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}
